/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package src;

import Model.Artist;
import Model.Musicgroupmember;
import Model.Song;
import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.WindowEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.EventObject;
import java.util.List;
import java.util.Objects;
import javax.imageio.ImageIO;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;

/**
 * Static μεθοδοι και πεδία για γενική χρήση
 *
 *
 */
public final class General {

    /**
     * private constructor !!! - δεν χρειάζονται instances
     */
    private General() {

    }

    //---------------------------------------------- Μόνο Static (!!!) μέθοδοι και πεδία 
    //--------------Πεδία
    public static final String dbMsgIntegrityViolation = "Διαγράψτε πρώτα τις εγγραφές από άλλους πίνακες που\n"
            + "εξαρτώνται από αυτήν την εγγραφή και δοκιμάστε ξανά.";
    public static final String dbTitleIntegrityViolation = "Παραβίαση ακεραιότητας ΒΔ";
    public static final String dbTitleUniqueConstrait = "Παραβίαση περιορισμού μοναδικότητας ΒΔ";
    public static final String dbMsgUniqueConstrait = "Η εγγραφή αυτή θα δημιουργούσε διπλότυπα στην ΒΔ";
    private static final EntityManager entityMangager = createEM();
    public static final String strUserName = System.getProperty("user.name");
    public static final SimpleDateFormat dateFormater = new SimpleDateFormat("dd/MM/yyyy");
    public static final SimpleDateFormat dateFormater2 = new SimpleDateFormat("dd MMMM yyyy");
    public static final SimpleDateFormat dateFormater3 = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss.SSSXXX");
    public static final String Empty = "";

    //---------------enums
    public enum XMLActionType {

        Export,
        Import
    };

    public enum ActionType {

        Insert,
        Update,
        Delete
    };
    //-----------------Μέθοδοι

    public static void CheckTextfield(String strTextfieldValue, String strTextfieldLabel, boolean blnIsMandatory, boolean blnCheckMaxLength, int intMaxLength, boolean blnCheckTrimmed) throws Exception {
        try {
            if ((blnIsMandatory && blnCheckTrimmed) && (strTextfieldValue == null || strTextfieldValue.isEmpty() || strTextfieldValue.trim().isEmpty())) {
                throw new Exception("Το πεδίο '" + strTextfieldLabel + "' είναι υποχρεωτικό!");
            } else if (blnIsMandatory && (strTextfieldValue == null || strTextfieldValue.isEmpty())) {
                throw new Exception("Το πεδίο '" + strTextfieldLabel + "' είναι υποχρεωτικό!");
            } else if (blnCheckMaxLength && blnCheckTrimmed && strTextfieldValue != null && strTextfieldValue.trim().length() > intMaxLength) {
                throw new Exception("Το πεδίο '" + strTextfieldLabel + "' δεν πρέπει να ξεπερνάει τους " + intMaxLength + " χαρακτήρες!");
            } else if (blnCheckMaxLength && strTextfieldValue != null && strTextfieldValue.length() > intMaxLength) {
                throw new Exception("Το πεδίο '" + strTextfieldLabel + "' δεν πρέπει να ξεπερνάει τους " + intMaxLength + " χαρακτήρες!");
            }
        } catch (Exception ex) {
            throw ex;
        }

    }

    /**
     * Κλείνει το παράθυρο προσομοιώνοντας την ίδια λειτουργία με το πάτημα του
     * Χ στην μπάρα τίτλου του παραθύρου Χρήση για για τα κουμπιά εξόδου των
     * φορμών. ΕΞΑΙΡΕΊΤΑΙ Η ΒΑΣΙΚΗ ΦΟΡΜΑ!!!!!! -> dispose()
     *
     * @param w
     */
    public static void closeWindowX(Window w) {
        w.dispatchEvent(new WindowEvent(w, WindowEvent.WINDOW_CLOSING));
    }

    /**
     * Για χρήση σε exception. Αν είναι exception ΒΔ εμφανίζει το κατάλληλο
     * μήνυμα αλλιώς το μήνυμα της exception
     *
     * @param ex
     * @param w
     */
    public static void showDbErrorMsgIf(Exception ex, Window w) {
        String msg = ex.getMessage();
        String title = "Error";
        if (General.isIntegrityMsg(ex.getMessage())) {
            msg = General.dbMsgIntegrityViolation;
            title = General.dbTitleIntegrityViolation;
        } else if (General.isUniqueMsg(ex.getMessage())) {
            msg = General.dbMsgUniqueConstrait;
            title = General.dbTitleUniqueConstrait;
        }
        General.showErrorMessage(msg, title, w);
    }

    /**
     * Επιστρέφει κλώνο του serializable
     *
     * @param <T>
     * @param serializable
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T> T getDeepCopy(T serializable) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream memStreamOut = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(memStreamOut);
        oos.writeObject(serializable);
        ByteArrayInputStream memStreamIn = new ByteArrayInputStream(memStreamOut.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(memStreamIn);
        return (T) ois.readObject();
    }

    /**
     * True αν το exceptionMsg είναι μήνυμα παραβίασης ακεραιότητας ΒΔ
     *
     * @param exceptionMsg
     * @return
     */
    public static boolean isIntegrityMsg(String exceptionMsg) {
        return exceptionMsg.contains("DELETE") && exceptionMsg.contains("constraint 'FK_");
    }

    /**
     * True αν το exceptionMsg είναι μήνυμα παραβίασης unique constraint της ΒΔ
     *
     * @param exceptionMsg
     * @return
     */
    public static boolean isUniqueMsg(String exceptionMsg) {
        return exceptionMsg.contains("duplicate key value") && exceptionMsg.contains("'UI_");
    }

    /**
     * Επιστρέφει το μοναδικό στιγμιότυπο του entityMangager
     *
     * @return
     */
    public static EntityManager getEntityManager() {
        return entityMangager;
    }

    /**
     * Insert στη ΒΔ
     *
     * @param entity
     * @param em
     */
    public static void insertToDB(Object entity, EntityManager em) {
        em.getTransaction().begin();
        em.persist(entity);
        em.getTransaction().commit();
    }

    /**
     * Ενημερώνει τη βάση με τις υπάρχουσες αλλαγές στο p. context
     *
     * @param em
     */
    public static void updateDB(EntityManager em) {
        em.getTransaction().begin();
        em.getTransaction().commit();
    }

    /**
     * Δημιουργεί τον entityManager
     *
     * @return
     */
    private static EntityManager createEM() {
        try {
            return Persistence.createEntityManagerFactory("RadioStationPU").createEntityManager();
        } catch (Exception e) {            
            General.showErrorMessage("Υπήρξε πρόβλημα κατά την σύνδεση με την βάση δεδομένων.\r\n\r\nΕλέγξτε την σύνδεση και προσπαθήστε ξανά!\r\n\r\nΗ εφαρμογή θα κλείσει!", "Σφάλμα σύνδεσης με ΒΔ", null);
            System.exit(1);
            return null;
        }
    }

    /**
     * Θέτει τα πεδία createdBy, createdDate στην λίστα από Τ entities
     *
     * @param <T>
     * @param list
     * @throws NoSuchFieldException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static <T> void setCreated(List<T> list) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        for (T t : list) {
            setCreated(t);
        }
    }

    public static <T> void setModified(List<T> list) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        for (T t : list) {
            setModified(t);
        }
    }

    public static void setCreated(Object obj) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        setObjectFieldValueByName(obj, "createdDate", new Date());
        setObjectFieldValueByName(obj, "createdBy", strUserName);
    }

    public static void setModified(Object obj) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        setObjectFieldValueByName(obj, "modifiedDate", new Date());
        setObjectFieldValueByName(obj, "modifiedBy", strUserName);
    }

    /**
     * Μορφοποιεί τη στήλη columnIndex του JTable jt με SimpleDateFormat
     *
     * @param columnIndex
     * @param jt
     */
    public static void assignSimpleDateFormatToJTableColumn(int columnIndex, JTable jt) {
        TableCellRenderer tcr = new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table,
                    Object value, boolean isSelected, boolean hasFocus,
                    int row, int column) {
                if (value instanceof Date) {
                    value = dateFormater.format(value);
                }
                return super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
            }
        };
        jt.getColumnModel().getColumn(columnIndex).setCellRenderer(tcr);
    }

    /**
     * Διαγράφει το entity με primary key ID, από τη ΒΔ
     *
     * @param ID
     * @param entity
     * @param em
     */
    public static void deleteFromDB(Long ID, Object entity, EntityManager em) {
        em.getTransaction().begin();
        em.remove(em.find(entity.getClass(), ID));
        em.getTransaction().commit();
    }

    /**
     * Θέτει την τιμή πεδίου σε Object με βάση το όνομα του πεδίου
     *
     * @param obj
     * @param fieldName
     * @param value
     * @throws NoSuchFieldException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static void setObjectFieldValueByName(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field f = obj.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(obj, value);
    }

    /**
     * True αν έχει επιλεγεί γραμμή από τον χρήστη στο jt. Αν όχι εμφανίζει
     * μήνυμα
     *
     * @param jt
     * @param entityNameOnomastikh
     * @param entityNameAitiatikh
     * @param parent
     * @return
     */
    public static boolean checkRowSelected(JTable jt, String entityNameOnomastikh, String entityNameAitiatikh, JFrame parent) {
        boolean selected = (jt.getSelectedRow() > -1);
        if (!selected) {
            showWarningMessage("Παρακαλώ επιλέξτε " + entityNameAitiatikh + ".", "Δεν επιλέχτηκε " + entityNameOnomastikh + ".", parent);
        }
        return selected;
    }

    /**
     * Απενεργοποιεί JTextFields και JComboBox στη φόρμα
     */
    public static void disableFieldsNCombos(final Container c) {
        ArrayList<JTextField> allJTextFields = getAllJTextField(c);
        ArrayList<JComboBox> allCombo = getAllJComboBox(c);
        for (JComboBox jcb : allCombo) {
            jcb.setEnabled(false);
        }
        for (JTextField jtf : allJTextFields) {
            jtf.setEnabled(false);
        }
    }

    /**
     * Επιστρέφει όλα τα JComboBox που ανήκουν στον c
     *
     * @param c
     * @return
     */
    public static ArrayList<JComboBox> getAllJComboBox(final Container c) {
        Component[] comps = c.getComponents();
        ArrayList<JComboBox> jComboList = new ArrayList<>();
        for (Component comp : comps) {
            if (comp instanceof JComboBox) {
                jComboList.add((JComboBox) comp);
            }
        }
        return jComboList;
    }

    /**
     * Επιστρέφει όλα τα JTextField που ανήκουν στον c
     *
     * @param c
     * @return
     */
    public static ArrayList<JTextField> getAllJTextField(final Container c) {
        Component[] comps = c.getComponents();
        ArrayList<JTextField> jTextFieldList = new ArrayList<>();
        for (Component comp : comps) {
            if (comp instanceof JTextField) {
                jTextFieldList.add((JTextField) comp);
            }
        }
        return jTextFieldList;
    }

    /**
     * Απενεργοποιεί τα κουμπιά επεξεργασίας και διαγραφής αν ο πίνακας είναι
     * κενός
     */
    public static void disableDeleteUpdateIf(JTable jt, JButton delete, JButton edit) {

        if (jt.getModel().getRowCount() == 0) {
            if (edit != null) {
                edit.setEnabled(false);
            }
            if (delete != null) {
                delete.setEnabled(false);
            }
        }
    }

    /**
     * Ενεργοποιεί τα κουμπιά επεξεργασίας και διαγραφής αν ο πίνακας δεν είναι
     * κενός
     */
    public static void enableDeleteUpdateIf(JTable jt, JButton delete, JButton edit) {

        if (jt.getModel().getRowCount() > 0) {
            if (edit != null) {
                edit.setEnabled(true);
            }
            if (delete != null) {
                delete.setEnabled(true);
            }

        }
    }

    /**
     * Τρέχει το named query και επιστρέφει τα αποτελέσματα σε T λίστα
     *
     * @param <T>
     * @param queryName
     * @param entityClass
     * @param em
     * @return
     */
    public static <T> List<T> runNamedQuery(String queryName, Class entityClass, EntityManager em) {
        TypedQuery<T> getEntityList = em.createNamedQuery(queryName, entityClass);
        return getEntityList.getResultList();
    }

    /**
     * Για κάθε JTextFierld του c ελέγχει το InputVerifier. Αν το text κάθε
     * πεδίου δεν περνάει τον έλεγχο εμφανίζει το σχετικό μήνυμα και επιστρέφει
     * false (καλείται στο actionperformed event στο κουμπί αποθήκευσης φόρμας
     * που περιέχει input από τον χρήστη)
     *
     * @param c
     * @param formTitle
     * @return
     */
    public static boolean checkJTFieldsInputVerification(Container c, String formTitle, Component parent) {
        List<JTextField> list = getAllJTextField(c);
        for (JTextField jtf : list) {
            if (!jtf.getText().equals(General.Empty) && !jtf.getInputVerifier().verify(jtf)) {
                showWarningMessage("Η μορφή των δεδομένων που εισάγατε δεν είναι αποδεκτή.\n Διορθώστε σύμφωνα με την υπόδειξη και ξαναδοκιμάστε αποθήκευση.", formTitle + " - Έλεγχος μορφής δεδομένων", parent);
                return false;
            }
        }
        return true;
    }

    /**
     * Επιλέγει το index του item του jcb του οποίου το item.ToString() είναι
     * ίσο με το το checkString. Θα πρέπει το entity που έχει τεθεί ως item να
     * υπερβαίνει την toString(), ώστε να δίνει το checkString
     *
     * @param jcb
     * @param checkString
     */
    public static void selectIndexIfItemToStringEquals(JComboBox jcb, String checkString) {
        for (int i = 0; i < jcb.getItemCount(); i++) {
            if (checkString.equals(jcb.getItemAt(i).toString())) {
                jcb.setSelectedIndex(i);
                break;
            }
        }
    }

    /**
     * Ελέγχει όλα τα fields (τα οποία πρέπει να είναι κλάσης JTextField ή
     * JComboBox) αν είναι κενά. Το mapping είναι το n στοιχείο της fields με το
     * n στοιχείο της columnNames
     *
     * @param columnNames
     * @param fields
     * @return
     * @throws Exception
     */
    public static boolean checkNotNulls(Object[] columnNames, JComponent[] fields, Window parent) throws Exception {
        if (columnNames.length != fields.length) {
            throw new Exception("Λάθος παράμετροι για μέθοδο checkNotNulls!");
        }
        for (JComponent f : fields) {
            if (!(f instanceof JComboBox) && !(f instanceof JTextField)) {
                throw new Exception("Εσωτερικό σφάλμα. Λάθος παράμετροι για μέθοδο checkNotNulls!");
            }
        }
        String msg = General.Empty;
        JComponent emptyComp = null;
        boolean result = true;
        for (int i = 0; i < fields.length; i++) {
            result = ((fields[i] instanceof JComboBox) ? ((JComboBox) fields[i]).getSelectedIndex() != -1
                    : !Empty.equals(((JTextField) fields[i]).getText()));
            if (!result) {
                msg = "Το πεδίο '" + columnNames[i] + "'" + " είναι απαραίτητο.";
                emptyComp = fields[i];
                break;
            }
        }
        if (!result) {
            showWarningMessage(msg, "Έλεγχος απαραίτητων πεδίων.", parent);
            emptyComp.requestFocus();
        }
        return result;
    }

    /**
     * Εμφανίζει ένα warning dialog
     *
     * @param text
     * @param title
     */
    public static void showWarningMessage(String text, String title, Component parent) {
        JOptionPane.showMessageDialog(parent, text, title, JOptionPane.WARNING_MESSAGE);
    }

    /**
     * Εμφανίζει ένα error dialog
     *
     * @param text
     * @param title
     */
    public static void showErrorMessage(String text, String title, Component parent) {
        JOptionPane.showMessageDialog(parent, text, title, JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Εμφανίζει ένα information dialog
     *
     * @param text
     * @param title
     */
    public static void showInfoMessage(String text, String title, Component parent) {
        JOptionPane.showMessageDialog(parent, text, title, JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Απενεργοποιεί όλους τους components του c
     *
     * @param c
     */
    public static void disableAllComponents(final Container c) {
        Component[] comps = c.getComponents();
        for (Component comp : comps) {
            comp.setEnabled(false);
        }
    }

    /**
     * Για περίπτωση DB exception
     *
     * @param em
     */
    public static void safeRollback(EntityManager em) {
        if (em.getTransaction().isActive()) {
            em.getTransaction().rollback();
        }
    }

    /**
     * Θέτει το εικονίδιο στην μπάρα τίτλου του παραθύρου
     *
     * @param w
     * @throws IOException
     */
    public static void setWindowImage(Window w) throws IOException {
        w.setIconImage(ImageIO.read(w.getClass().getResource("/Images/note1.png")));
    }

    /**
     * Επιστρέφει το index της επιλεγμένης (από τον χρήστη) γραμμής στο
     * TableModel
     *
     * @param jt
     * @return
     */
    public static int getSelectedRowModelIndex(JTable jt) {
        return jt.convertRowIndexToModel(jt.getSelectedRow());
    }

    /**
     * Κάνει την artistList οbservable και bind με το jt
     *
     * @param artistList
     * @param jt
     * @return
     */
    public static List<Artist> bindTableToObservableArtistList(List<Artist> artistList, JTable jt) {
        artistList = (List<Artist>) org.jdesktop.observablecollections.ObservableCollections.observableList(artistList);
        org.jdesktop.swingbinding.JTableBinding jTableBinding = org.jdesktop.swingbinding.SwingBindings.createJTableBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, artistList, jt);
        org.jdesktop.swingbinding.JTableBinding.ColumnBinding columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${lastname}"));
        columnBinding.setColumnName("Επώνυμο");
        columnBinding.setColumnClass(String.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${firstname}"));
        columnBinding.setColumnName("Όνομα");
        columnBinding.setColumnClass(String.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${artisticName}"));
        columnBinding.setColumnName("Καλλιτεχνικό Όνομα");
        columnBinding.setColumnClass(String.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${sexId}"));
        columnBinding.setColumnName("Φύλο");
        columnBinding.setColumnClass(Model.Sex.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${birthday}"));
        columnBinding.setColumnName("Ημ/νία γέννησης");
        columnBinding.setColumnClass(java.util.Date.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${birthPlace}"));
        columnBinding.setColumnName("Τόπος γέννησης");
        columnBinding.setColumnClass(String.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${musicGenreId}"));
        columnBinding.setColumnName("Μουσικό είδος");
        columnBinding.setColumnClass(Model.Musicgenre.class);
        jTableBinding.bind();
        General.assignSimpleDateFormatToJTableColumn(4, jt);
        return artistList;
    }

    /**
     * Εμφανιζει dialog με επιλογές NAI, OXI
     *
     * @param text
     * @param title
     * @param parent
     * @return
     */
    public static int showYesNoMessage(String text, String title, Component parent) {
        String[] options = {"NAI", "OXI"};
        return JOptionPane.showOptionDialog(parent, text, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
    }

    public static Musicgroupmember getGroupMemberByArtistID(List<Musicgroupmember> list, Long artistId) {
        for (Musicgroupmember mgm : list) {
            if (Objects.equals(mgm.getArtistId().getArtistId(), artistId)) {
                return mgm;
            }
        }
        return null;
    }

    public static Song songGetByTitleNotCaseSensitive(List<Song> list, String title) {
        for (Song s : list) {
            if (s.getSongTitle().toUpperCase().equals(title.toUpperCase())) {
                return s;
            }
        }
        return null;
    }

    public static int songIndexOfByTitleNotCaseSensitive(List<Song> list, String title) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getSongTitle().toUpperCase().equals(title.toUpperCase())) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Κάνει το jt read-only
     *
     * @param jt
     */
    public static void setJTableNotEditable(JTable jt) {
        jt.setDefaultEditor(Object.class, new DefaultCellEditor(new JTextField()) {
            @Override
            public boolean isCellEditable(EventObject eo) {
                return false;
            }
        });
    }

    /**
     * Επιλέγει την γραμή του JTable και τη φέρνει στο ορατό πεδίο
     *
     * @param modelRowIndex
     * @param jt
     */
    public static void selectModelRowNVisible(int modelRowIndex, JTable jt) {
        int index = jt.convertRowIndexToView(modelRowIndex);
        jt.setRowSelectionInterval(index, index);
        Rectangle rect = jt.getCellRect(index, 0, true);
        jt.scrollRectToVisible(rect);
    }

    /**
     * Επιλέγει κελί του JTable και το φέρνει στο ορατό πεδίο
     *
     * @param modelRowIndex
     * @param columnIndex
     * @param jt
     */
    public static void selectModelCellNVisible(int modelRowIndex, int columnIndex, JTable jt) {
        int index = jt.convertRowIndexToView(modelRowIndex);
        jt.changeSelection(index, columnIndex, false, false);
        Rectangle rect = jt.getCellRect(index, 0, true);
        jt.scrollRectToVisible(rect);
    }

}
